﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace SpeakupBackend.Themes
{
    [Serializable]
    public sealed class Level : IEquatable<Level>, 
                                IComparable, 
                                IComparable<Level>,
                                ISerializable
    {
        
        public const int MinValidLevelNumber = 1;
        public const int MaxValidLevelNumber = 15;

        public const int UnitsPerLevel = 5;
        public const int MinValidUnitNumber = 1;
        public const int MaxValidUnitNumber = MaxValidLevelNumber * UnitsPerLevel;
        
        static List<Level> AvailableLevels = new List<Level>();

        static Level()
        {
            for (int i = MinValidLevelNumber; i <= MaxValidLevelNumber; i++)
            {
                AvailableLevels.Add(new Level(i));
            }
        }

        public static bool IsValidLevelNumber(int levelNumber)
        {
            return MinValidLevelNumber <= levelNumber && levelNumber <= MaxValidLevelNumber;
        }

        public static bool IsValidUnitNumber(int unitNumber)
        {
            return MinValidUnitNumber <= unitNumber && unitNumber <= MaxValidUnitNumber;
        }
        
        public static Level GetLevel(int levelNumber)
        {
            if (!IsValidLevelNumber(levelNumber))
            {
                throw new ArgumentException("LevelNumber");
            }
            return AvailableLevels[levelNumber - 1];
        }

        public static Level GetLevelByUnit(int unit)
        {
            if (unit < MinValidUnitNumber || MaxValidUnitNumber < unit )
            {
                throw new ArgumentException("Unit");
            }

            int levelNumber = (unit - 1)/ UnitsPerLevel + 1;
            return GetLevel(levelNumber);
        }

        public static Level GetLevel(String strLevel)
        {
            int level = 0;
            if (!Int32.TryParse(strLevel, NumberStyles.Integer, CultureInfo.InvariantCulture, out level))
            {
                throw new FormatException(String.Format("Can't parse {0} to a level number", strLevel));
            }

            return GetLevel(level);
        }

        public static IList<Level> GetLevels()
        {
            return AvailableLevels.AsReadOnly();
        }
        

        readonly int m_levelNumber;
        public int LevelNumber
        {
            get
            {
                return m_levelNumber;
            }
        }

        private Level(int levelNumber)
        {
            if (!IsValidLevelNumber(levelNumber))
            {
                throw new ArgumentException("LevelNumber");
            }
            m_levelNumber = levelNumber;
        }

        public LevelGroup LevelGroup
        {
            get
            {
                return LevelGroup.DefaultFactory.GetLevelGroup(this);
            }
        }

        public IList<int> GetUnits()
        {
            List<int> units = new List<int>();

            int minUnit = (LevelNumber - 1) * UnitsPerLevel + 1;
            int maxUnit = (LevelNumber - 1) * UnitsPerLevel + UnitsPerLevel;
            for(int i = minUnit; i <= maxUnit; i++)
            {
                units.Add(i);
            };

            return units;
        }

        public bool ContainsUnit(int unit)
        {
            int minUnit = (LevelNumber - 1) * UnitsPerLevel + 1;
            int maxUnit = (LevelNumber - 1) * UnitsPerLevel + UnitsPerLevel;

            return minUnit <= unit && unit <= maxUnit;
        }
        
        public override bool Equals(object obj)
        {
            return Equals(obj as Level);
        }

        public bool Equals(Level other)
        {
            return ClassHelper.IEquatableEquals(this, other);
        }

        public override int GetHashCode()
        {
            return LevelNumber.GetHashCode();
        }

        public override string ToString()
        {
            return LevelNumber.ToString();
        }

        public int CompareTo(object obj)
        {
            return CompareTo((Level)obj);
        }

        public int CompareTo(Level other)
        {
            return LevelNumber - other.LevelNumber;
        }      

        #region Comparison operators

        static public bool operator == (Level rhs, Level lhs)
        {
            return ClassHelper.OperatorEqual(rhs, lhs);
        }

        static public bool operator !=(Level rhs, Level lhs)
        {
            return ClassHelper.OperatorNotEqual(rhs, lhs);
        }

        static public bool operator >(Level rhs, Level lhs)
        {
            return rhs.CompareTo(lhs) > 0;
        }

        static public bool operator <(Level rhs, Level lhs)
        {
            return rhs.CompareTo(lhs) < 0;
        }

        static public bool operator >=(Level rhs, Level lhs)
        {
            return rhs.CompareTo(lhs) >= 0;
        }

        static public bool operator <=(Level rhs, Level lhs)
        {
            return rhs.CompareTo(lhs) <= 0;
        }

        #endregion

        #region Serialization

        static readonly string LevelNumberSerializationKey = "LevelNumber";

        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.SetType(typeof(SerializationHelper));
            info.AddValue(LevelNumberSerializationKey, LevelNumber);
        }

        [Serializable]
        private sealed class SerializationHelper: IObjectReference, ISerializable
        {
            int LevelNumber;

            SerializationHelper(SerializationInfo info, StreamingContext context)
            {
                LevelNumber = info.GetInt32(LevelNumberSerializationKey);
            }

            Object IObjectReference.GetRealObject(StreamingContext context)
            {
                return Level.GetLevel(LevelNumber);
            }

            void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
            {
                throw new NotImplementedException();
            }
        }

        #endregion
    }
}
